using Sidvall.Business;
using Sidvall.Data;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Sidvall.UI.Entities
{
    public abstract class BusinessBase : IDataRowContextProvider, IOnPropertyChanged, IBusinessSettingsProvider, INotifyPropertyChanged, INotifyChildChanged,
        INotifyDataErrorInfo, IBusinessRulesProvider, IValidParentProvider
    {
        #region Public Members

        public event System.EventHandler<ChildChangedEventArgs> ChildChanged;
        public event System.EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        public event PropertyChangedEventHandler PropertyChanged;

        #region DataRowBusinessRules

        private BusinessRuleCollection _DataRowBusinessRules;
        public BusinessRuleCollection DataRowBusinessRules
        {
            get
            {
                if (_DataRowBusinessRules == null)
                {
                    lock (_Mutex)
                    {
                        if (_DataRowBusinessRules == null)
                        {
                            var items = new BusinessRuleCollection();
                            items.BusinessRulePropertyChanged += OnDataRowBusinessRulesPropertyChanged;
                            _DataRowBusinessRules = items;
                        }
                    }
                }
                return _DataRowBusinessRules;
            }
        }

        #endregion
        #region DataRowStateIsDeleted

        public virtual bool DataRowStateIsDeleted
        {
            get
            {
                if (this.DataRowContext.DataRowState == DataRowState.Deleted)
                    return true;
                return false;
            }
        }

        #endregion
        #region DataRowStateIsDirty

        public virtual bool DataRowStateIsDirty
        {
            get
            {
                return this.DataRowStateIsSelfDirty;
            }
        }

        #endregion
        #region DataRowStateIsSelfDirty

        public virtual bool DataRowStateIsSelfDirty
        {
            get
            {
                return (this.DataRowContext.DataRowState != DataRowState.Unchanged);
            }
        }

        #endregion
        #region DataRowStateIsNew

        public virtual bool DataRowStateIsNew
        {
            get
            {
                if (this.DataRowContext.DataRowState == DataRowState.Added)
                    return true;
                return false;
            }
        }

        #endregion

        // Protected
        #region BusinessSettings

        private System.Lazy<BusinessSettings> _BusinessSettings = new System.Lazy<BusinessSettings>(BusinessSettings.CreateSettingsAll);
        protected BusinessSettings BusinessSettings
        {
            get
            {
                return _BusinessSettings.Value;
            }
        }

        #endregion
        #region DataRowContextKey

        protected virtual string DataRowContextKey
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(this.DataRowContext.DataRowKey))
                    return this.DataRowContext.DataRowKey;
                return this.DataRowContextStorageKey;
            }
            set
            {
                this.DataRowContext.DataRowKey = value;
            }
        }

        #endregion
        #region DataRowContextStorageKey

        protected virtual string DataRowContextStorageKey
        {
            get
            {
                return null;
            }
        }

        #endregion

        #region ClearDataRowBusinessRules

        protected virtual void ClearDataRowBusinessRules()
        {
            this.DataRowBusinessRules.Clear();
        }

        #endregion
        #region DataRowIsValid

        public bool DataRowIsValid()
        {
            return DataRowIsValid(BusinessRuleFilter.CreateFilter());
        }
        public virtual bool DataRowIsValid(BusinessRuleFilter policy)
        {
            return DataRowIsSelfValid(policy);
        }

        #endregion
        #region DataRowIsSelfValid

        public bool DataRowIsSelfValid()
        {
            return DataRowIsSelfValid(BusinessRuleFilter.CreateFilter());
        }
        public bool DataRowIsSelfValid(BusinessRuleFilter policy)
        {
            return this.DataRowBusinessRules.IsValid(policy);
        }

        #endregion
        #region GetDataRowBusinessRulesForProperty

        protected virtual BusinessRuleCollection GetDataRowBusinessRulesForProperty(string propertyName)
        {
            var items = new BusinessRuleCollection();
            if (!string.IsNullOrWhiteSpace(propertyName))
                items.AddRange(true, this.DataRowBusinessRules.FindBusinessRulesForProperties(true, propertyName));
            else
                items.AddRange(true, this.DataRowBusinessRules.FindBusinessRulesForProperties(true));
            return items;
        }

        #endregion
        #region OnChildChanged

        protected void OnChildChanged(object child, PropertyChangedEventArgs childArgs)
        {
            if (this.BusinessSettings.EnabledBusinessEvents.HasFlag(BusinessEvents.NotifyChildChanged))
            {
                var args = new ChildChangedEventArgs(child, childArgs);
                OnChildChanged(args);
            }
        }
        protected void OnChildChanged(object child, NotifyCollectionChangedEventArgs childArgs)
        {
            if (this.BusinessSettings.EnabledBusinessEvents.HasFlag(BusinessEvents.NotifyChildChanged))
            {
                var args = new ChildChangedEventArgs(child, childArgs);
                OnChildChanged(args);
            }
        }
        protected void OnChildChanged(object sender, ChildChangedEventArgs e)
        {
            if (this.BusinessSettings.EnabledBusinessEvents.HasFlag(BusinessEvents.NotifyChildChanged))
                OnChildChanged(e);
        }
        protected virtual void OnChildChanged(ChildChangedEventArgs args)
        {
            if (this.ChildChanged != null)
                this.ChildChanged.Invoke(this, args);
            if (!string.IsNullOrWhiteSpace(args?.PropertyChangedArgs?.PropertyName))
            {
                switch (args?.PropertyChangedArgs?.PropertyName)
                {
                    case nameof(BusinessBase.DataRowStateIsDirty):
                        OnPropertyChanged(nameof(DataRowStateIsDirty), true, false);
                        break;
                    default:
                        break;
                }
            }
            else
            {
                OnPropertyChanged(nameof(DataRowStateIsDirty), true, false);
            }
        }

        #endregion
        #region OnDataRowBusinessRulesPropertyChanged

        protected void OnDataRowBusinessRulesPropertyChanged(object sender, BusinessRulePropertyEventArgs e)
        {
            if (e == null)
                throw new System.ArgumentNullException(nameof(e));
            _ClearErrors = true;
            foreach (var propertyName in e.PropertyNames)
                OnErrorsChanged(propertyName);
            _ClearErrors = false;
            foreach (var propertyName in e.PropertyNames)
                OnErrorsChanged(propertyName);
        }

        #endregion
        #region OnErrorsChanged

        protected void OnErrorsChanged(string propertyName)
        {
            OnErrorsChanged(new DataErrorsChangedEventArgs(propertyName));
        }
        protected virtual void OnErrorsChanged(DataErrorsChangedEventArgs args)
        {
            if (this.BusinessSettings.EnabledBusinessEvents.HasFlag(BusinessEvents.NotifyErrorsChanged))
            {
                if (this.ErrorsChanged != null)
                    this.ErrorsChanged.Invoke(this, args);
            }
        }

        #endregion
        #region OnPropertyChanged

        protected void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName, true, true);
        }
        protected void OnPropertyChanged(string propertyName, bool raisePropertyChanged, bool updateState)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName), raisePropertyChanged, updateState);
        }
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs args, bool raisePropertyChanged, bool updateState)
        {
            if ((raisePropertyChanged) && (this.BusinessSettings.EnabledBusinessEvents.HasFlag(BusinessEvents.NotifyPropertyChanged)))
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged.Invoke(this, args);
            }
            if ((updateState) && (this.BusinessSettings.EnabledBusinessEvents.HasFlag(BusinessEvents.UpdateState)))
            {
                if (this.DataRowContext.DataRowState == DataRowState.Unchanged)
                {
                    var dataRowStateIsSelfDirty = this.DataRowStateIsSelfDirty;
                    var dataRowStateIsDirty = this.DataRowStateIsDirty;
                    this.DataRowContext.DataRowState = DataRowState.Modified;
                    if ((!dataRowStateIsSelfDirty) && (this.DataRowStateIsSelfDirty))
                        OnPropertyChanged(nameof(DataRowStateIsSelfDirty), true, false);
                    if ((!dataRowStateIsDirty) && (this.DataRowStateIsDirty))
                        OnPropertyChanged(nameof(DataRowStateIsDirty), true, false);
                }
            }
        }

        #endregion
        #region SetDataRowContext

        protected virtual void SetDataRowContext(IDataRowContextProvider item, DataRowState? dataRowState)
        {
            this.DataRowContext.Import(item, dataRowState);
        }
        protected virtual void SetDataRowContext(IDataRowContextProvider item, DataRowState? dataRowState, string dataRowKey)
        {
            this.DataRowContext.Import(item, dataRowState, dataRowKey);
        }

        #endregion
        #region UpdateChildProperty

        protected void UpdateChildProperty(object oldValue, object newValue)
        {
            RemoveEventHooks(oldValue);
            AddEventHooks(newValue);
        }

        #endregion

        #region IBusinessRulesProvider Members

        BusinessRuleCollection IBusinessRulesProvider.BusinessRules
        {
            get
            {
                return this.DataRowBusinessRules;
            }
        }

        #endregion
        #region IBusinessEventProvider Members

        BusinessSettings IBusinessSettingsProvider.BusinessSettings
        {
            get
            {
                return this.BusinessSettings;
            }
        }

        #endregion
        #region IDataRowContextProvider Members

        string IDataRowContextProvider.DataRowContextKey
        {
            get
            {
                return this.DataRowContextKey;
            }
            set
            {
                this.DataRowContextKey = value;
            }
        }
        string IDataRowContextProvider.DataRowContextStorageKey
        {
            get
            {
                return this.DataRowContextStorageKey;
            }
        }

        bool IDataRowContextProvider.DataRowStateIsDeleted
        {
            get
            {
                return this.DataRowStateIsDeleted;
            }
        }

        bool IDataRowContextProvider.DataRowStateIsDirty
        {
            get
            {
                return this.DataRowStateIsDirty;
            }
        }

        bool IDataRowContextProvider.DataRowStateIsSelfDirty
        {
            get
            {
                return this.DataRowStateIsSelfDirty;
            }
        }

        bool IDataRowContextProvider.DataRowStateIsNew
        {
            get
            {
                return this.DataRowStateIsNew;
            }
        }

        void IDataRowContextProvider.SetDataRowContext(IDataRowContextProvider item, DataRowState? dataRowState)
        {
            SetDataRowContext(item, dataRowState);
        }

        void IDataRowContextProvider.SetDataRowContext(IDataRowContextProvider item, DataRowState? dataRowState, string dataRowKey)
        {
            SetDataRowContext(item, dataRowState, dataRowKey);
        }

        #endregion
        #region INotifyDataErrorInfo Members

        System.Collections.IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
        {
            if (_ClearErrors)
                return null;
            var items = GetDataRowBusinessRulesForProperty(propertyName);
            if (items.Count == 0)
                return null;
            var value = new List<BusinessRuleCollection>();
            value.Add(items);
            return value;
        }
        bool INotifyDataErrorInfo.HasErrors
        {
            get
            {
                return this.DataRowBusinessRules.Count > 0;
            }
        }

        #endregion
        #region IOnPropertyChanged Members

        void IOnPropertyChanged.OnPropertyChanged(string propertyName, bool raisePropertyChanged, bool updateState)
        {
            OnPropertyChanged(propertyName, raisePropertyChanged, updateState);
        }

        #endregion
        #region IValidParentProvider Members

        bool IValidParentProvider.IsValid(BusinessRuleFilter policy)
        {
            return DataRowIsValid(policy);
        }
        bool IValidParentProvider.IsSelfValid(BusinessRuleFilter policy)
        {
            return DataRowIsSelfValid(policy);
        }

        #endregion

        #endregion
        #region Private Members

        private readonly object _Mutex = new object();
        private bool _ClearErrors = false;

        #region DataRowContext

        private DataRowContext _DataRowContext;
        private DataRowContext DataRowContext
        {
            get
            {
                if (_DataRowContext == null)
                    _DataRowContext = new DataRowContext();
                return _DataRowContext;
            }
        }

        #endregion

        #region AddEventHooks

        private void AddEventHooks(object item)
        {
            if (item == null)
                return;
            var pc = item as INotifyPropertyChanged;
            if (pc != null)
                pc.PropertyChanged += OnChildChanged;
            var ncc = item as INotifyCollectionChanged;
            if (ncc != null)
                ncc.CollectionChanged += OnChildChanged;
            var cc = item as INotifyChildChanged;
            if (cc != null)
                cc.ChildChanged += OnChildChanged;
        }

        #endregion
        #region RemoveEventHooks

        private void RemoveEventHooks(object item)
        {
            if (item == null)
                return;
            var pc = item as INotifyPropertyChanged;
            if (pc != null)
                pc.PropertyChanged -= OnChildChanged;
            var ncc = item as INotifyCollectionChanged;
            if (ncc != null)
                ncc.CollectionChanged -= OnChildChanged;
            var cc = item as INotifyChildChanged;
            if (cc != null)
                cc.ChildChanged -= OnChildChanged;
        }

        #endregion

        #endregion
        #region Constructors

        protected BusinessBase()
        {
        }
        protected BusinessBase(DataRowState? dataRowState)
            : this(dataRowState, null)
        {
        }
        protected BusinessBase(DataRowState? dataRowState, string dataRowKey)
        {
            if (dataRowState != null)
                this._DataRowContext = new DataRowContext(dataRowState.Value, dataRowKey);
        }

        #endregion
    }
}
